<template>
  <div class="validate-input-container pb-3">
    <input
      v-if="tag === 'input'"
      v-bind="$attrs"
      class="form-control"
      :class="{ 'is-invalid': error }"
      @blur="validate"
      v-model="val"
    />
    <textarea
      v-if="tag === 'textarea'"
      v-bind="$attrs"
      class="form-control"
      :class="{ 'is-invalid': error }"
      @blur="validate"
      v-model="val"
    />
    <div class="invalid-feedback" v-if="error">{{ message }}</div>
  </div>
</template>

<script lang='ts'>
import {
  defineComponent,
  PropType,
  reactive,
  toRefs,
  onMounted,
  computed
} from 'vue'
import { emitter } from './ValidateForm.vue'

interface RuleProp {
  type: 'required' | 'email' | 'custom';
  message: string;
  validator?: () => Promise<boolean>;
}

export type RulesProp = RuleProp[];

export type TagProp = 'input' | 'textarea';

const emailReg = new RegExp(
  '^[a-z0-9A-Z]+[- | a-z0-9A-Z . _]+@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-z]{2,}$'
)

export default defineComponent({
  name: 'ValidateInput',
  inheritAttrs: false,
  props: {
    rules: {
      type: Array as PropType<RulesProp>
    },
    modelValue: String,
    tag: {
      type: String as PropType<TagProp>,
      default: 'input'
    }
  },
  emits: ['update:modelValue'],
  setup (props, context) {
    const state = reactive({
      val: computed<string>({
        get () {
          return props.modelValue || ''
        },
        set (v) {
          context.emit('update:modelValue', v)
        }
      }),
      error: false,
      message: ''
    })

    const validate = () => {
      return new Promise<boolean>((resolve) => {
        if (props.rules) {
          state.error = false
          state.message = ''
          for (let i = 0; i < props.rules.length; i++) {
            const item = props.rules[i]
            switch (item.type) {
              case 'required':
                if (state.val === '') {
                  if (!state.error) {
                    state.error = true
                    state.message = item.message
                  }
                  resolve(false)
                }
                break
              case 'email':
                if (!emailReg.test(state.val)) {
                  if (!state.error) {
                    state.error = true
                    state.message = item.message
                  }
                  resolve(false)
                }
                break
              case 'custom':
                if (item.validator) {
                  item
                    .validator()
                    .then((res) => {
                      if (!res) {
                        if (!state.error) {
                          state.error = true
                          state.message = item.message
                        }
                        resolve(false)
                      }
                    })
                    .catch(() => {
                      if (!state.error) {
                        state.error = true
                        state.message = '验证失败'
                      }
                      resolve(false)
                    })
                }
                break
              default:
                break
            }
          }
          setTimeout(() => {
            resolve(true)
          })
        } else {
          resolve(true)
        }
      })
    }

    const clear = () => {
      if (state.error) {
        state.val = ''
        state.error = false
        state.message = ''
      }
    }

    onMounted(() => {
      emitter.emit('push:validateFunc', validate)
      emitter.emit('push:clearFunc', clear)
    })

    return {
      ...toRefs(state),
      validate
    }
  }
})
</script>

<style scoped>
.validate-input-container {
  position: relative;
}

.invalid-feedback {
  position: absolute;
}
</style>
